package it.antonio.ilpizze.db.core;

import it.antonio.ilpizze.db.BtreeIndex;
import it.antonio.ilpizze.db.DbException;
import it.antonio.ilpizze.db.Id;
import it.antonio.ilpizze.db.Ids;
import it.antonio.ilpizze.db.core.LockManager;
import it.antonio.ilpizze.db.core.StatusManager;
import it.antonio.ilpizze.db.core.TransactionManager;
import it.antonio.ilpizze.db.index.btree.BTree;
import it.antonio.ilpizze.db.index.btree.BTreeBrowser;
import it.antonio.ilpizze.db.io.IOFile;
import it.antonio.ilpizze.func.ComputeIterator;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


public class FileBtreeIndex<K extends Comparable<K>> implements BtreeIndex<K> {
	
	private BTree<K, Id> btree;
	private TransactionManager txManager;
	private StatusManager statusManager;
	private LockManager lockManager;
	
	public FileBtreeIndex(BTree<K, Id> btree, TransactionManager txManager, StatusManager statusManager, LockManager lockManager) {
		this.btree = btree;
		this.txManager = txManager;
		this.statusManager = statusManager;
		this.lockManager = lockManager;
	}

	
	@Override
	public Id get(final K key) {
		checkReadStatus();
		lockManager.checkMultiThreadingRead();
		
		Id id =  btree.get(key);
		
		return id;
	}
	
	@Override
	public void remove(final K key) {
		checkWriteStatus();
		lockManager.checkMultiThreadingWrite();
		
		final Id id = btree.remove(key);
		if(txManager.autoCommit() == false){
			txManager.add(new TransactionUnit() {
				
				@Override
				public void commit() {
					
				}
				
				@Override
				public void rollback() {
					btree.put(key, id);
				}
				
				@Override
				public void log(IOFile txLog) {
					
				}
				
				
			});
		}
	}
	
	@Override
	public void put(final K key, Id id) {
		checkWriteStatus();
		lockManager.checkMultiThreadingWrite();
		
		btree.put(key, id);
		
		if(txManager.autoCommit() == false){
			txManager.add(new TransactionUnit() {
				
				@Override
				public void commit() {
					
				}
				
				@Override
				public void rollback() {
					btree.remove(key);
				}
				
				@Override
				public void log(IOFile txLog) {
					
				}
				
				
			});
		}
	}
	
	@Override
	public Ids iterator(final K key) {
		checkReadStatus();
		lockManager.checkMultiThreadingRead();
		
		final BTreeBrowser<K, Id> browser = btree.browse(key);
		
		return new Ids() {
			
			private boolean iterating = false;
			
			@Override
			public Iterator<Id> iterator() {
				return new ComputeIterator<Id>() {

					@Override
					protected Id computeNext() {
						iterating = true;
						if(browser.hasNext()){
							return browser.next().second();
						} else {
							return finished();	
						}
					}
				};
			}
			
			@Override
			public Id first() {
				checkAlreadyIterated();
				Iterator<Id> it = iterator();
				if(it.hasNext()){
					return it.next();
				}
				endIteration();
				return null;
			}

			@Override
			public List<Id> list() {
				checkAlreadyIterated();
				List<Id> ids = new ArrayList<Id>();
				for(Id id: this){
					ids.add(id);
				}
				endIteration();
				return ids;
			}

			@Override
			public List<Id> list(int num) {
				checkAlreadyIterated();
				List<Id> ids = new ArrayList<Id>();
				int count = 0;
				for(Id id: this){
					ids.add(id);
					count++;
					if(count == num) return ids;
				}
				endIteration();
				return ids;
			}
			
			@Override
			public void endIteration() {
				
			}
			
			private void checkAlreadyIterated(){
				if(iterating == true) throw new DbException("Iterator started");
			}
		};
		
	}
	
	private void checkReadStatus(){
		if(txManager.autoCommit() == false) {
			statusManager.checkStatus(Status.OPEN, Status.TRANSACTION);
		} else {
			statusManager.checkStatus(Status.OPEN);
		}
	}
	
	
	private void checkWriteStatus(){
		if(txManager.autoCommit() == false) {
			statusManager.checkStatus(Status.TRANSACTION);
		} else {
			statusManager.checkStatus(Status.OPEN);
		}
	}
	
}
